package algorithm.sort;

import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;

/**
 * 希尔排序
 * 　希尔排序也是一种插入排序，它是简单插入排序经过改进之后的一个更高效的版本，也称为缩小增量排序，同时该算法是冲破O(n2）的第一批算法之一。
 *   希尔排序是把记录按下标的一定增量分组，对每组使用直接插入排序算法排序；随着增量逐渐减少，每组包含的关键词越来越多，当增量减至1时，整个文件恰被分成一组，算法便终止。
 * 　简单插入排序很循规蹈矩，不管数组分布是怎么样的，依然一步一步的对元素进行比较，移动，插入，
 * 比如[5,4,3,2,1,0]这种倒序序列，数组末端的0要回到首位置很是费劲，比较和移动元素均需n-1次。而希尔排序在数组中采用跳跃式分组的策略，
 *
 * 通过某个增量将数组元素划分为若干组，然后分组进行插入排序，随后逐步缩小增量，继续按组进行插入排序操作，直至增量为1。
 * 希尔排序通过这种策略使得整个数组在初始阶段达到从宏观上看基本有序，小的基本在前，大的基本在后。然后缩小增量，到增量为1时，
 * 其实多数情况下只需微调即可，不会涉及过多的数据移动。
 *
 * 基本步骤，选择增量gap=length/2，缩小增量继续以gap = gap/2的方式，
 * 这种增量选择我们可以用一个序列来表示，{n/2,(n/2)/2...1}，称为增量序列。希尔排序的增量序列的选择与证明是个数学难题，
 * 我们选择的这个增量序列是比较常用的，也是希尔建议的增量，称为希尔增量，但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。
 *
 * 我们上面选择的增量序列{n/2,(n/2)/2...1}(希尔增量)，其最坏时间复杂度依然为O(n2)，一些经过优化的增量序列如Hibbard经过复杂证明可使得最坏时间复杂度为O(n3/2)
 */
public class ShellSort {

    private final int[] arr = new int[7];

    @Before
    public void init() {
        for (int i = 0, j = arr.length; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * j);
        }
    }

    @Test
    public void sort() {
        System.out.println("排序前:" + Arrays.toString(arr));
        //增量gap，并逐步缩小增量
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            //从第gap个元素，逐个对其所在组进行直接插入排序操作
            for (int i = gap; i < arr.length; i++) {
                int j = i;
                while (j - gap >= 0 && arr[j] < arr[j - gap]) {
                    //插入排序采用交换法
                    arr[j] = arr[j] ^ arr[j - gap];
                    arr[j - gap] = arr[j] ^ arr[j - gap];
                    arr[j] = arr[j] ^ arr[j - gap];
                    j -= gap;
                }
            }
            System.out.println("排序后:" + Arrays.toString(arr));
        }
    }
}
